#include <fstream>

typedef unsigned char uint8_t;

// adjust these to control endiannesses.
//
static const int ENDIAN0 = 0;
static const int ENDIAN1 = 1;
static const int ENDIAN2 = 2;
static const int ENDIAN3 = 3;

static const int ENDIAN0_S = 0;
static const int ENDIAN1_S = 1;

// desired wave structure - AHEM
// - THESE CAN'T BE CHANGED WITHOUT FUTZING THE CODE. SORRY.
//
static const int CHANNELS = 2;
static const int SAMPLERATE = 44100;
static const int BITSPERSAMPLE = 16;

// volume. 1.0 is loudest, 0.0 is silence.
//
static const double maxLevel = (double)SHRT_MAX * 1.0;
static const short maxLevelS = (short)maxLevel;
static const double minLevel = (double)SHRT_MIN * 1.0;
static const short minLevelS = (short)minLevel;

// number of samples per cycle. 60 samples = 1 low or 2 high cycles.
//
static const int f1 = 60;
static const int f1cyc = 1;
static const int f2 = f1 / 2;
static const int f2cyc = f1cyc * 2;

// useful values, maybe
//
static const int usPerCycle = (int)(((double)f1 / 44100) * 1000000.0);
static const int usPerHalfCycle = (int)(((double)f1 / 44100) * 500000.0);
static const int usPerQuarterCycle = (int)(((double)f1 / 44100) * 250000.0);
static const int usPerEighthCycle = (int)(((double)f1 / 44100) * 125000.0);

static const int baudrate = (1000000 / usPerCycle);

// 8 + stop + start;
//
static const int bitsPerFrame = 10;

static const int samplesPerBit = f1 * f1cyc;
static const int samplesPerFrame = bitsPerFrame * samplesPerBit;


static const int LOW_BIT = 0;
static const int HIGH_BIT = !LOW_BIT;

static const int LEADIN_BIT = LOW_BIT;
static const int START_BIT = !LEADIN_BIT;
static const int STOP_BIT = !START_BIT;


// NASTY HACK FOR WRITING WAVS. PLEASE LOOK AWAY NOW.

void CreateWaveFile(std::ostream& output)
{
    char chars[4];

    output << "RIFF";
    *(int*)chars = 16;
    output << chars[ENDIAN0] << chars[ENDIAN1] << chars[ENDIAN2] << chars[ENDIAN3];

    output << "WAVE";
    output << "fmt ";

    *(int*)chars = 16;
    output << chars[ENDIAN0] << chars[ENDIAN1] << chars[ENDIAN2] << chars[ENDIAN3];

    *(short*)chars = 1;
    output << chars[ENDIAN0_S] << chars[ENDIAN1_S];

    *(short*)chars = CHANNELS;
    output << chars[ENDIAN0_S] << chars[ENDIAN1_S];

    *(int*)chars = SAMPLERATE;
    output << chars[ENDIAN0] << chars[ENDIAN1] << chars[ENDIAN2] << chars[ENDIAN3];

    *(int*)chars = (int)(SAMPLERATE*(BITSPERSAMPLE/8*CHANNELS));
    output << chars[ENDIAN0] << chars[ENDIAN1] << chars[ENDIAN2] << chars[ENDIAN3];

    *(short*)chars = (short)(BITSPERSAMPLE/8*CHANNELS);
    output << chars[ENDIAN0_S] << chars[ENDIAN1_S];

    *(short*)chars = (short)BITSPERSAMPLE;
    output << chars[ENDIAN0_S] << chars[ENDIAN1_S];

    output << "data";
    *(int*)chars = 0;
    output << chars[ENDIAN0] << chars[ENDIAN1] << chars[ENDIAN2] << chars[ENDIAN3];
}


void FinaliseWaveFile(std::ostream& output, size_t bytesWrit)
{
    char chars[4];

    output.seekp(40);

    *(int*)chars = (int)bytesWrit;
    output << chars[ENDIAN0] << chars[ENDIAN1] << chars[ENDIAN2] << chars[ENDIAN3];

    output.seekp(4);

    *(int*)chars = (int)(bytesWrit + 36);
    output << chars[ENDIAN0] << chars[ENDIAN1] << chars[ENDIAN2] << chars[ENDIAN3];
}

// OK IT'S SAFE TO LOOK AGAIN



// here we represent:
//
//   0 bit as 1 cycle of low frequency,
//   1 bit as 2 cycles of high frequency
//
// where the high frequency is double that of the low.
//
void OutputBit(std::ostream& output, int bit, size_t& written)
{
    // default to '0'
    //
    int cycles = f1cyc;
    int samplesPerCycle = f1;

    if (bit != 0)
    {
        cycles = f2cyc;
        samplesPerCycle = f2;
    }

    for (int i = 0; i < cycles; ++i)
    {
        for (int j = 0; j < samplesPerCycle / 2; ++j)
        {
            // 1
            output << uint8_t(maxLevelS & 0xff);
            output << uint8_t(maxLevelS >> 8);
            output << uint8_t(maxLevelS & 0xff);
            output << uint8_t(maxLevelS >> 8);
            written += 4;
        }
        for (int j = 0; j < samplesPerCycle / 2; ++j)
        {
            // 0
            output << uint8_t(minLevelS & 0xff);
            output << uint8_t(minLevelS >> 8);
            output << uint8_t(minLevelS & 0xff);
            output << uint8_t(minLevelS >> 8);
            written += 4;
        }
    }
}


// outputs a start bit, 8 data bits and a stop bit
//
void OutputByte(std::ostream& output, uint8_t value, size_t& written)
{
    OutputBit(output, START_BIT, written);

    for (int i = 0; i < 8; ++i)
    {
        OutputBit(output, (value & 0x80) ? HIGH_BIT : LOW_BIT, written);
        value <<= 1;
    }

    OutputBit(output, STOP_BIT, written);
}


// outputs a lead train of bits, terminated with a start bit.
//
void OutputLeader(std::ostream& output, int milliseconds, size_t& written)
{
    int microseconds = (milliseconds * 1000) - usPerCycle;;

    while (microseconds >= 0)
    {
        OutputBit(output, LEADIN_BIT, written);

        microseconds -= usPerCycle;
    }

    OutputBit(output, START_BIT, written);
}



void main (int argc, char **argv)
{
    std::ofstream outer("test.wav", std::ios_base::binary);

    CreateWaveFile(outer);

    // replace with size to write
    size_t size = 1000;

    // index into data
    //
    size_t pc = 0;

    size_t written = 0;

    // file is structured:
    //   2 seconds of leader
    //   2 bytes of size msb first
    //   data as 512 byte blocks separated by a 50 millisecond leader if more bytes follow

    OutputLeader(outer, 2000, written);

    OutputByte(outer, uint8_t(size >> 8), written);
    OutputByte(outer, uint8_t(size & 0xff), written);

    while (pc != size)
    {
        // dummy data
        OutputByte(outer, uint8_t((pc & 63) | ((pc / 512)<<6)), written);

        ++pc;
        if (pc % 512 == 0 && pc != size)
        {
            OutputLeader(outer, 50, written);
        }
    }

    FinaliseWaveFile(outer, written);

    outer.close();
}